﻿using FlyingEye.Extensions;
using FlyingEye.HttpServices;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Server.Kestrel.Core;
using Microsoft.AspNetCore.Server.Kestrel.Https;
using System.Net;

#if DEBUG
using Microsoft.OpenApi.Models;
#endif
using System.Reflection;
using System.Security.Authentication;
using System.Security.Cryptography.X509Certificates;
using Volo.Abp;
using Volo.Abp.AspNetCore.ExceptionHandling;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.ExceptionHandling;
using Volo.Abp.Autofac;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Modularity;

namespace FlyingEye
{
    [DependsOn(
        typeof(DeviceOpsServiceApplicationModule),
        typeof(AbpAspNetCoreMvcModule),
        typeof(AbpAutofacModule))]
    public class DeviceOpsServiceModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            // 添加在模块配置中
            Configure<AbpExceptionHttpStatusCodeOptions>(options =>
            {
                options.Map("Volo.Abp:010400", HttpStatusCode.BadRequest);
            });

            ApiVersionExtensions.AddApiVersioning(context.Services);

            // 设置应用程序启动路径，一定是基于主函数所在的模块作为启动路径。
            var startPath = Path.GetDirectoryName(typeof(DeviceOpsServiceModule).Assembly.Location);

            if (string.IsNullOrWhiteSpace(startPath) || !Directory.Exists(startPath))
            {
                throw new InvalidOperationException("无效的启动路径！");
            }

            var hostingEnvironment = context.Services.GetHostingEnvironment();
            var configuration = context.Services.GetConfiguration();

            // 配置 http 客户端服务选项
            context.Services.Configure<HttpClientServiceOptions>(configuration.GetSection(key: nameof(HttpClientServiceOptions)));
            context.Services.Configure<DeviceClientServiceOptions>(configuration.GetSection(key: nameof(DeviceClientServiceOptions)));

            context.Services.AddCors(options =>
            {
                options.AddDefaultPolicy(builder =>
                {
                    builder
                        .WithOrigins(
                            configuration["App:CorsOrigins"]
                                ?.Split(",", StringSplitOptions.RemoveEmptyEntries)
                                .Select(o => o.RemovePostFix("/"))
                                .ToArray() ?? throw new InvalidOperationException("The App:CorsOrigins is null.")
                        )
                        .WithAbpExposedHeaders()
                        .SetIsOriginAllowedToAllowWildcardSubdomains()
                        .AllowAnyHeader()
                        .AllowAnyMethod()
                        .AllowCredentials();
                });
            });

            // 配置 https 证书
            Configure<KestrelServerOptions>(options =>
                {
                    var port = configuration["Https:Port"]
                           ?? throw new InvalidOperationException("The Https:Port is null or empty.");

                    options.ListenAnyIP(int.Parse(port), options =>
                    {
                        var baseDir = Path.GetDirectoryName(typeof(DeviceOpsServiceModule).Assembly.Location)
                            ?? throw new InvalidOperationException("程序集 DeviceOpsServiceModule 目录为空！");

                        var pfxFile = Path.Combine(baseDir, configuration["Https:Certificate"]
                            ?? throw new InvalidOperationException("The Https:Certificate is null or empty."));
                        var password = configuration["Https:Password"]
                            ?? throw new InvalidOperationException("The Https:Password is null or empty.");
                        if (!File.Exists(pfxFile)) throw new InvalidOperationException($"{pfxFile} is not found.");

                        var cert = new X509Certificate2(pfxFile, password);
                        options.UseHttps(cert);
                    });

                    options.ConfigureHttpsDefaults(listenOptions =>
                    {
                        listenOptions.ClientCertificateMode = ClientCertificateMode.AllowCertificate;
                        listenOptions.SslProtocols = SslProtocols.Tls13 | SslProtocols.Tls12;
                    });
                });

            // 添加 JWT 认证
            context.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, options =>
                {
                    var oauth = configuration.GetSection("OAuth");

                    // 必须是 https 访问
                    options.RequireHttpsMetadata = true;
                    // 您的 IdentityServer 的基地址
                    options.Authority = oauth["Authority"];

                    // 配置验证 issuer 
                    options.TokenValidationParameters.ValidIssuer = oauth["ValidIssuer"];
                    options.TokenValidationParameters.ValidateIssuer = true;
                    // access_token 使用的是 API 资源，可以在此处指定名称资源名称，然后允许 access_token 验证
                    // id_token 使用的是 ClientId 作为 aud，可以在此指定 ClientId，然后允许 id_token 验证
                    options.TokenValidationParameters.ValidAudiences = [oauth["Audience"], oauth["ClientId"],];
                    options.TokenValidationParameters.ValidateAudience = true;

                    var jwkFile = Path.Combine(startPath, configuration["SigningKey:Path"]
                        ?? throw new InvalidOperationException("The SigningKey:Path is null or empty."));

                    if (!File.Exists(jwkFile)) throw new InvalidOperationException($"{jwkFile} is not found.");

                    var jsonString = File.ReadAllText(jwkFile);
                    var jwk = new Microsoft.IdentityModel.Tokens.JsonWebKey(jsonString);

                    options.TokenValidationParameters.IssuerSigningKey = jwk;
                    options.TokenValidationParameters.ValidateIssuerSigningKey = true;

                    // 默认情况下 IdentityServer 发出一个 typ 标头，建议额外检查
                    // id_token 的 typ 标头是 JWT
                    options.TokenValidationParameters.ValidTypes = new[] { "at+jwt", "JWT" };
                });

            context.Services.AddSignalR();
#if DEBUG
            // 如果使用 swagger，最小 api 模型，应该添加这个中间件
            // 详见：https://stackoverflow.com/questions/71932980/what-is-addendpointsapiexplorer-in-asp-net-core-6/71933535#71933535
            context.Services.AddEndpointsApiExplorer();
            context.Services.AddSwaggerGen(options =>
            {
                options.SwaggerDoc("v1", new OpenApiInfo { Title = "DeviceOpsService", Version = "v1" });
                var xmlFilename = $"{Assembly.GetExecutingAssembly().GetName().Name}.xml";
                options.IncludeXmlComments(Path.Combine(startPath, xmlFilename));

                // 添加 jwt 认证配置到 swagger
                var securityScheme = new OpenApiSecurityScheme
                {
                    Name = "Authorization",
                    Type = SecuritySchemeType.Http,
                    Scheme = "Bearer",
                    BearerFormat = "at+jwt"
                };
                options.AddSecurityDefinition("Bearer", securityScheme);
                options.AddSecurityRequirement(new OpenApiSecurityRequirement
                {
                    {
                        new OpenApiSecurityScheme
                        {
                            Reference = new OpenApiReference
                            {
                                Type = ReferenceType.SecurityScheme,
                                Id = "Bearer"
                            }
                        },
                        new string[] { }
                    }
                });
            });
#endif
            base.ConfigureServices(context);
        }

        public override void OnApplicationInitialization(ApplicationInitializationContext context)
        {
            var app = context.GetApplicationBuilder();
            var env = context.GetEnvironment();

            // 使用异常处理
            app.UseAbpExceptionHandling();
            app.UseHsts();

            app.UseRouting();
#if DEBUG
            if (env.IsDevelopment())
            {
                app.UseSwagger();
                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint("/swagger/v1/swagger.json", "FlyingEyeWebApi");
                });
            }
#endif
            app.UseCors(); // 启用跨域策略
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(options =>
            {
                options.MapControllers();
            });

            base.OnApplicationInitialization(context);
        }
    }
}
